/**
* project3D Engine
* @author John Sword
* @version 2 - AS3
*/

package engine.camera
{

	import engine.geom.Vector;
	import engine.math.Matrix3x3;
	import engine.math.VectorUtils;
	
	import flash.display.Sprite;
	
	public class Camera3D
	{
	
		public var w:Number = 0;
		public var h:Number = 0;
		public var eyeX:Number = 0;
		public var eyeY:Number = 0;
		public var eyeZ:Number = 0;
		public var zClipping:Number = 2;
		public var renderMe:Boolean = true;
		private var position:Vector;
		private var rotation_matrix:Matrix3x3;
		private var side:Vector;
		private var up:Vector;
		private var out:Vector;
		private var target:Vector;
		private static const sin:Function = Math.sin;
		private static const cos:Function = Math.cos;
		
		public function Camera3D ( s:Sprite, focus:Number = undefined, pos:Vector = null )
		{
			w = s.width;
			h = s.height;
			// On monitors usually (0,0) is at the top left corner of the monitor. 
			// To put the picture in the middle of the screen, 
			// half the screen width must be added to x and half the screen height must be added to y.
			eyeX = w / 2;
			eyeY = h / 2;
			if (focus) eyeZ = focus
			else eyeZ = w;
			if (!pos) 
			{
				position = new Vector ();
			}
			// cam position/rotation vectors
			side = new Vector (1.0, 0.0, 0.0);
			up   = new Vector (0.0, 1.0, 0.0);
			out  = new Vector (0.0, 0.0, 1.0);
			// rotation matrix formed from the camera that will be applied to points
			rotation_matrix = new Matrix3x3 ();
			target = new Vector ();
		}
		
		public function setPosition (v:Vector) : void
		{
			position = v;
			renderMe = true;
		}
		
		public function getPosition () : Vector
		{
			return position;
		}
		
		// translate the camera along the x-axis
		public function translateX (a:Number) : void
		{
			position.x += a;
			renderMe = true;
		}

		// translate the camera along the y-axis
		public function translateY (a:Number) : void
		{
			position.y += a;
			renderMe = true;
		}


		// translate the camera along the z-axis
		public function translateZ (a:Number) : void
		{
			position.z += a;
			renderMe = true;
		}
		
		public function moveXAxis(ammount:Number) : void
		{
			position.x += (side.x * ammount) >> 0;
			position.y += (side.y * ammount) >> 0;
			position.z += (side.z * ammount) >> 0;
			renderMe = true;
		}

		
		public function moveYAxis (ammount:Number) : void
		{
			position.x -= (up.x * ammount) >> 0;
			position.y -= (up.y * ammount) >> 0;
			position.z -= (up.z * ammount) >> 0;
			renderMe = true;
		}

		
		public function moveZAxis (ammount:Number) : void
		{
			position.x -= (out.x * ammount) >> 0;
			position.y -= (out.y * ammount) >> 0;
			position.z -= (out.z * ammount) >> 0;
			renderMe = true;
		}
		
		
		// rotates the 'up' and 'out' vectors about the 'side' vector (pitches the camera)
		public function Pitch (a:Number) : void
		{
			// rotation matrix that will be applied to the 'up' and 'out' vectors
			var matrix:Matrix3x3 = new Matrix3x3 ();
			
			// load the elements of the rotation matrix
			matrix.load_rotation_axis (side, sin(a), cos(a));
			
			// rotate the 'up' and 'out' vectors
			up  = matrix.vector_multiplication (up);
			out = matrix.vector_multiplication (out);
			renderMe = true;
		}
		
		// rotates the 'side' and 'out' vectors about the 'up' vector (yaws the camera)
		public function Yaw (a:Number) : void
		{
			// rotation matrix that will be applied to the 'side' and 'out' vectors
			var matrix:Matrix3x3 = new Matrix3x3 ();
			
			// load the elements of the rotation matrix
			matrix.load_rotation_axis (up, sin(a), cos(a));
			
			// rotate the 'side' and 'out' vectors
			side = matrix.vector_multiplication (side);
			out  = matrix.vector_multiplication (out);
			renderMe = true;
		}


		// rotates the 'side' and 'up' vectors about the 'out' vector (rolls the camera)
		public function Roll (a:Number) : void
		{
			// rotation matrix that will be applied to the 'side' and 'up' vectors
			var matrix:Matrix3x3 = new Matrix3x3 ();
			
			// load the elements of the rotation matrix
			matrix.load_rotation_axis (out, sin(a), cos(a));
			
			// rotate the 'side' and 'up' vectors
			side = matrix.vector_multiplication (side);
			up   = matrix.vector_multiplication (up);
			renderMe = true;
		}
		
		
		// rotates the camera around the world x-axis
		public function rotateX (a:Number) : void
		{
			// rotation matrix that will be applied to the camera
			var matrix:Matrix3x3 = new Matrix3x3 ();
			
			// load the elements of the rotation matrix
			matrix.load_rotation_x (sin(a), cos(a));
			
			// rotate the camera
			side = matrix.vector_multiplication (side);
			up   = matrix.vector_multiplication (up);
			out  = matrix.vector_multiplication (out);
			renderMe = true;
		}
		
		
		// rotates the camera around the world y-axis
		public function rotateY (a:Number) : void
		{
			// rotation matrix that will be applied to the camera
			var matrix:Matrix3x3 = new Matrix3x3 ();
			
			// load the elements of the rotation matrix
			matrix.load_rotation_y (sin(a), cos(a));
			
			// rotate the camera
			side = matrix.vector_multiplication (side);
			up   = matrix.vector_multiplication (up);
			out  = matrix.vector_multiplication (out);
			renderMe = true;
		}
		
		
		// rotates the camera around the world z-axis
		public function rotateZ (a:Number) : void
		{
			// rotation matrix that will be applied to the camera
			var matrix:Matrix3x3 = new Matrix3x3 ();
			
			// load the elements of the rotation matrix
			matrix.load_rotation_z (sin(a), cos(a));
			
			// rotate the camera
			side = matrix.vector_multiplication (side);
			up   = matrix.vector_multiplication (up);
			out  = matrix.vector_multiplication (out);
			renderMe = true;
		}

		
		// makes the camera look at the point
		public function lookAt (v:Vector) : void
		{
			var v:Vector = new Vector(v.x,v.y,v.z);

			// set the out vector so that the camera is looking at the point
			out = VectorUtils.Normalize(VectorUtils.difference(v,position));

			// calculate the side vector from the out vector and the world y-axis (world up vector)
			side = VectorUtils.Normalize(VectorUtils.cross(out,new Vector(0.0, 1.0, 0.0)));

			// calculate the up vector from the out and side camera vectors
			up = VectorUtils.Normalize( VectorUtils.cross(side,out) );
			side = VectorUtils.negate(side);
			renderMe = true;
		}
		
		// rotates around center using degrees and a rotation vector
		// the rotation is fixed
		public function angleRotate ( a:Vector ) : void
		{
			resetRotationVectors();
			// load the vector into the rotation matrix
			rotation_matrix.angleRotation( a );
			// rotate
			side = rotation_matrix.vector_multiplication (side);
			up   = rotation_matrix.vector_multiplication (up);
			out  = rotation_matrix.vector_multiplication (out);
			renderMe = true;
		}
		
		public function resetRotationVectors () : void
		{
			side = new Vector (1.0, 0.0, 0.0);
			up   = new Vector (0.0, 1.0, 0.0);
			out  = new Vector (0.0, 0.0, 1.0);
		}
		
		// calculates the internal rotation matrix of the camera (must be called before a point can be transformed)
		public function update_rotation_matrix () : void
		{
			if ( !renderMe ) return;
			// set the elements of the rotation matrix
			rotation_matrix.aa = side.x;
			rotation_matrix.ab = side.y;
			rotation_matrix.ac = side.z;
			
			rotation_matrix.ba = up.x;
			rotation_matrix.bb = up.y;
			rotation_matrix.bc = up.z;

			rotation_matrix.ca = out.x;
			rotation_matrix.cb = out.y;
			rotation_matrix.cc = out.z;
		}
		
		public function getRotationMatrix() : Matrix3x3
		{
			return rotation_matrix;
		}
		
		public function get X() : Number 
		{
			return position.x;
		}
		
		public function set X ( value:Number ) : void 
		{
			position.x = value;
			renderMe = true;
		}
	
		public function get Y() : Number 
		{
			return position.y;
		}
		
		public function set Y ( value:Number ) : void 
		{
			position.y = value;
			renderMe = true;
		}
	
		public function get Z() : Number 
		{
			return position.z;
		}
		
		public function set Z ( value:Number ) : void 
		{
			position.z = value;
			renderMe = true;
		}
		
		public function unproject ( x:Number, y:Number ) : Vector
		{
			var zoom:Number = 1;
			var focus:Number = eyeZ;
			var persp:Number = (focus*zoom) / (focus);
            var v:Vector = new Vector(x/persp, y/persp, focus);
            //Matrix3D.multiplyVector3x3(transform, v);
            var m:Matrix3x3 = rotation_matrix.clone();
            v = m.vector_multiplication(v);
            return v;
		}
		
	}
	
}